package com.atguigu.gmall.activity.service.impl;

import com.atguigu.gmall.activity.mapper.SeckillGoodsMapper;
import com.atguigu.gmall.activity.model.OrderRecode;
import com.atguigu.gmall.activity.model.SeckillGoods;
import com.atguigu.gmall.activity.model.UserRecode;
import com.atguigu.gmall.activity.service.SeckillGoodsService;
import com.atguigu.gmall.activity.util.CacheHelper;
import com.atguigu.gmall.common.constant.MqConst;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.common.result.ResultCodeEnum;
import com.atguigu.gmall.common.service.RabbitService;
import com.atguigu.gmall.common.util.DateUtil;
import com.atguigu.gmall.common.util.MD5;
import com.atguigu.gmall.order.client.OrderFeignClient;
import com.atguigu.gmall.order.model.OrderDetail;
import com.atguigu.gmall.order.model.OrderInfo;
import com.atguigu.gmall.user.client.UserFeignClient;
import com.atguigu.gmall.user.model.UserAddress;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.commons.lang.StringUtils;
import org.aspectj.weaver.ast.Or;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.BoundListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author: atguigu
 * @create: 2023-03-17 09:41
 */
@Service
public class SeckillGoodsServiceImpl extends ServiceImpl<SeckillGoodsMapper, SeckillGoods> implements SeckillGoodsService {


    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RabbitService rabbitService;

    @Autowired
    private UserFeignClient userFeignClient;

    @Autowired
    private OrderFeignClient orderFeignClient;


    /**
     * 查询Redis中当日秒杀商品列表
     *
     * @return
     */
    @Override
    public List<SeckillGoods> getSeckillGoodsList() {
        String seckillGoodsKey = RedisConst.SECKILL_GOODS;
        BoundHashOperations<String, String, SeckillGoods> hashOps = redisTemplate.boundHashOps(seckillGoodsKey);
        return hashOps.values();
    }

    /**
     * 查询指定秒杀商品信息
     *
     * @param skuId
     * @return
     */
    @Override
    public SeckillGoods getSeckillGoods(Long skuId) {
        String seckillGoodsKey = RedisConst.SECKILL_GOODS;
        BoundHashOperations<String, String, SeckillGoods> hashOps = redisTemplate.boundHashOps(seckillGoodsKey);
        if (hashOps.hasKey(skuId.toString())) {
            return hashOps.get(skuId.toString());
        }
        return null;
    }


    /**
     * 为当前用户生成当前抢购商品抢购码
     *
     * @param skuId
     * @return
     */
    @Override
    public Result getSeckillSkuIdStr(Long skuId, String userId) {
        //1.验证秒杀商品状态-查询本地缓存
        String state = (String) CacheHelper.get(skuId.toString());
        if (StringUtils.isBlank(state)) {
            return Result.build(null, ResultCodeEnum.ILLEGAL_REQUEST);
        }
        if ("0".equals(state)) {
            return Result.build(null, ResultCodeEnum.SECKILL_FINISH);
        }
        if ("1".equals(state)) {
            //2.验证秒杀商品时间
            SeckillGoods seckillGoods = this.getSeckillGoods(skuId);
            Date now = new Date();
            if (DateUtil.dateCompare(seckillGoods.getStartTime(), now) && DateUtil.dateCompare(now, seckillGoods.getEndTime())) {
                //3.生成抢购码 将来用户秒杀时候会进行验证 todo 考虑将用户抢购码存入Redis
                String encrypt = MD5.encrypt(userId + skuId);
                return Result.ok(encrypt);
            }
        }
        return Result.build(null, ResultCodeEnum.ILLEGAL_REQUEST);
    }

    /**
     * 秒杀下单预处理(不处理真实秒杀业务,将秒杀购买意向发送MQ)
     *
     * @param skuId    购买商品
     * @param skuIdStr 抢购码
     * @return
     */
    @Override
    public Result seckillPre(String userId, Long skuId, String skuIdStr) {
        //1.验证用户提交的抢购码是否在正确
        //1.1 根据用户提交参数生成抢购码,跟用户提交的比较
        String encrypt = MD5.encrypt(userId + skuId);
        if (StringUtils.isBlank(encrypt) || !encrypt.equals(skuIdStr)) {
            return Result.build(null, ResultCodeEnum.ILLEGAL_REQUEST);
        }

        //2.验证本地缓存中秒杀商品状态 是否为"1"
        String state = (String) CacheHelper.get(skuId.toString());
        if (StringUtils.isBlank(state) || "0".equals(state)) {
            return Result.build(null, ResultCodeEnum.ILLEGAL_REQUEST);
        }

        if ("1".equals(state)) {
            //3.构建用户秒杀商品购买意向,将秒杀请求放入消息队列
            UserRecode userRecode = new UserRecode();
            userRecode.setUserId(userId);
            userRecode.setSkuId(skuId);
            rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_SECKILL_USER, MqConst.ROUTING_SECKILL_USER, userRecode);
            return Result.ok();
        }
        return Result.build(null, ResultCodeEnum.ILLEGAL_REQUEST);
    }

    /**
     * 处理秒杀队列中下单请求,秒杀订单预处理
     *
     * @param userRecode
     */
    @Override
    public void processSeckillRequest(UserRecode userRecode) {
        //1.从本地缓存中获取商品状态 1:可以继续 0:结束
        String state = (String) CacheHelper.get(userRecode.getSkuId().toString());
        if (StringUtils.isBlank(state)) {
            //秒杀商品以及失效
            return;
        }
        if ("0".equals(state)) {
            //商品已售罄
            return;
        }
        //2.判断用户是否重复下单(是否在排队中)-将用户购买商品存入redis todo:时间截止到今日0点
        String ifQueueKey = RedisConst.SECKILL_USER + userRecode.getUserId() + ":" + userRecode.getSkuId();
        Boolean ifQueue = redisTemplate.opsForValue().setIfAbsent(ifQueueKey, userRecode.getSkuId(), 10, TimeUnit.HOURS);
        if (!ifQueue) {
            //说明用户购买指定商品重复排队
            return;
        }
        //3.从Redis中商品库存List队列中弹数据同时库存List数量减少 成功:秒到(具备购买资格) 失败:售罄-更新本地缓存
        String skuStockKey = RedisConst.SECKILL_STOCK_PREFIX + userRecode.getSkuId();
        BoundListOperations<String, String> stockListOps = redisTemplate.boundListOps(skuStockKey);
        String goodsId = stockListOps.rightPop();
        if (StringUtils.isBlank(goodsId)) {
            //说明分布式缓存中商品库存售罄 发送redis订阅通知,通知每个秒杀服务实例更新本地缓存中商品状态
            String msg = userRecode.getSkuId() + ":0";
            redisTemplate.convertAndSend("seckillpush", msg);
            return;
        }
        //4.生成临时订单(具备抢购资格) 将临时订单存入Redis
        OrderRecode orderRecode = new OrderRecode();
        orderRecode.setUserId(userRecode.getUserId());
        orderRecode.setSeckillGoods(this.getSeckillGoods(userRecode.getSkuId()));
        orderRecode.setNum(1);
        //生成临时订单编号 唯一
        String orderStr = MD5.encrypt(orderRecode.getUserId() + userRecode.getSkuId());
        orderRecode.setOrderStr(orderStr);
        //构建临时订单hash结构的key
        String tempOrderKey = RedisConst.SECKILL_ORDERS;
        BoundHashOperations<String, String, OrderRecode> tempOrderHashOps = redisTemplate.boundHashOps(tempOrderKey);
        //临时订单存储到redis
        tempOrderHashOps.put(userRecode.getUserId(), orderRecode);

        //5.发送更新库存数 消息到MQ
        rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_SECKILL_STOCK, MqConst.ROUTING_SECKILL_STOCK, userRecode.getSkuId());

        //6.TODO 发送延迟消息,用户在5分钟内没有产生订单 恢复秒杀库存  消息内容:userRecode(包含用户ID+商品Id)
    }


    /**
     * 秒杀成功扣库秒杀商品库存
     *
     * @param skuId
     */
    @Override
    public void deductStock(Long skuId) {
        //1.查询Redis中商品库存List长度--商品最新库存
        String stockListKey = RedisConst.SECKILL_STOCK_PREFIX + skuId;
        BoundListOperations<String, String> stockListOps = redisTemplate.boundListOps(stockListKey);
        Long stockCount = stockListOps.size();
        //2.更新Redis中秒杀商品hash结构中 秒杀信息库存数量
        String seckillGoodsKey = RedisConst.SECKILL_GOODS;
        BoundHashOperations<String, String, SeckillGoods> seckillHashOps = redisTemplate.boundHashOps(seckillGoodsKey);
        SeckillGoods seckillGoods = seckillHashOps.get(skuId.toString());
        if (seckillGoods != null) {
            seckillGoods.setStockCount(stockCount.intValue());
            seckillHashOps.put(skuId.toString(), seckillGoods);

            //3.更新数据库中秒杀商品库存数量
            this.updateById(seckillGoods);
        }
    }

    /**
     * 前端定时发起检查当前用户秒杀商品结果
     *
     * @param skuId 秒杀商品ID
     * @return
     */
    @Override
    public Result checkOrderResult(String userId, Long skuId) {
        //查询用户对秒杀商品秒杀结果状态:根据响应业务状态码显示不同提示
        //1.判断用户是否在排队,有机会秒杀成功
        String ifQueueKey = RedisConst.SECKILL_USER + userId + ":" + skuId;
        Boolean ifQueue = redisTemplate.hasKey(ifQueueKey);
        if (ifQueue) {
            //2.判断用户是否提交了订单-218 code
            String orderKey = RedisConst.SECKILL_ORDERS_USERS;
            BoundHashOperations<String, String, Long> orderHashOps = redisTemplate.boundHashOps(orderKey);
            if (orderHashOps.hasKey(userId)) {
                //用户提交秒杀订单 获取秒杀成功后订单ID,将订单ID返回
                Long orderId = orderHashOps.get(userId);
                return Result.build(orderId, ResultCodeEnum.SECKILL_ORDER_SUCCESS);
            }

            //3.判断用户是否产生临时订单 - 215code
            String tempOrderKey = RedisConst.SECKILL_ORDERS;
            BoundHashOperations<String, String, OrderRecode> tempOrderHashOps = redisTemplate.boundHashOps(tempOrderKey);
            if (tempOrderHashOps.hasKey(userId)) {
                //用户抢单成功(获取到秒杀下单资格) 将临时订单数据返回
                OrderRecode orderRecode = tempOrderHashOps.get(userId);
                return Result.build(orderRecode, ResultCodeEnum.SECKILL_SUCCESS);
            }
        }

        //4.判断本地缓存中商品状态:0:售罄 1:code:211
        String state = (String) CacheHelper.get(skuId.toString());
        if ("0".equals(state)) {
            return Result.build(null, ResultCodeEnum.SECKILL_FINISH);
        }

        //5.其他情况 211 code
        return Result.build(null, ResultCodeEnum.SECKILL_RUN);
    }


    /**
     * 获取当前登录用户秒杀订单确认页面相关数据
     * ${detailArrayList} 秒杀商品清单
     * ${totalNum}总数
     * ${totalAmount}总金额
     * ${userAddressList} 收件地址列表
     *
     * @return
     */
    @Override
    public Map<String, Object> getSeckillOrderTradeDatas(String userId) {
        Map<String, Object> tradeMap = new HashMap<>();
        //1.封装秒杀清单数据
        // 1.1 先根据用户ID从临时订单中获取秒杀商品信息
        String tempOrderKey = RedisConst.SECKILL_ORDERS;
        BoundHashOperations<String, String, OrderRecode> tempOrderHashOps = redisTemplate.boundHashOps(tempOrderKey);
        if (tempOrderHashOps.hasKey(userId)) {
            // 1.2 将秒杀商品SeckillGoods转为OrderDetail对象
            OrderRecode orderRecode = tempOrderHashOps.get(userId);
            if (orderRecode != null) {
                SeckillGoods seckillGoods = orderRecode.getSeckillGoods();
                if (seckillGoods != null) {
                    OrderDetail orderDetail = new OrderDetail();
                    orderDetail.setImgUrl(seckillGoods.getSkuDefaultImg());
                    orderDetail.setSkuName(seckillGoods.getSkuName());
                    orderDetail.setOrderPrice(seckillGoods.getCostPrice());
                    orderDetail.setSkuNum(orderRecode.getNum());
                    List<OrderDetail> orderDetailList = Arrays.asList(orderDetail);
                    tradeMap.put("detailArrayList", orderDetailList);
                    //2. 封装商品总数+商品总金额
                    tradeMap.put("totalNum", orderRecode.getNum());

                    OrderInfo orderInfo = new OrderInfo();
                    orderInfo.setOrderDetailList(orderDetailList);
                    orderInfo.sumTotalAmount();
                    tradeMap.put("totalAmount", orderInfo.getTotalAmount());
                }
            }
        }
        //3.封装收件地址列表:远程调用用户微服务查询收件地址列表
        List<UserAddress> userAddressList = userFeignClient.getUserAddressListByUserId(Long.valueOf(userId));
        if (!CollectionUtils.isEmpty(userAddressList)) {
            tradeMap.put("userAddressList", userAddressList);
        }
        return tradeMap;
    }

    /**
     * 保存秒杀订单
     *
     * @param orderInfo
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long submitSeckillOrder(OrderInfo orderInfo) {
        Long userId = orderInfo.getUserId();
        //1.远程调用订单微服务保存秒杀订单
        Long orderId = orderFeignClient.submitSeckillOrder(orderInfo);
        //2.删除用户秒杀过程中产生临时订单
        String tempOrderKey = RedisConst.SECKILL_ORDERS;
        BoundHashOperations<String, String, OrderRecode> tempOrderHashOps = redisTemplate.boundHashOps(tempOrderKey);
        if (tempOrderHashOps.hasKey(userId.toString())) {
            tempOrderHashOps.delete(userId.toString());
        }
        //3.保存用户秒杀订单记录存储到Redis中
        String seckillOrderKey = RedisConst.SECKILL_ORDERS_USERS;
        BoundHashOperations<String, String, Long> seckillOrderHashOps = redisTemplate.boundHashOps(seckillOrderKey);
        seckillOrderHashOps.put(userId.toString(), orderId);
        return orderId;
    }

    /**
     * 清理秒杀当天产生缓存数据+数据库秒杀商品状态
     */
    @Override
    public void processCleanCache() {
        //1.获取当日参与秒杀所有商品-从缓存Hash结构中
        String seckillGoodsKey = RedisConst.SECKILL_GOODS;
        BoundHashOperations<String, String, SeckillGoods> seckillGoodsHashOps = redisTemplate.boundHashOps(seckillGoodsKey);
        List<SeckillGoods> seckillGoodsList = seckillGoodsHashOps.values();
        if (!CollectionUtils.isEmpty(seckillGoodsList)) {
            //2.清除每个商品库存List结构
            for (SeckillGoods seckillGoods : seckillGoodsList) {
                String stockListKey = RedisConst.SECKILL_STOCK_PREFIX + seckillGoods.getSkuId();
                redisTemplate.delete(stockListKey);
                //6.修改数据库中秒杀商品状态 改为 "2"
                seckillGoods.setStatus("2");
                this.updateById(seckillGoods);
            }
        }
        //3.清理所有产生秒杀订单缓存hash
        String seckillOrderKey = RedisConst.SECKILL_ORDERS_USERS;
        redisTemplate.delete(seckillOrderKey);
        //4.删除秒杀商品Hash
        redisTemplate.delete(seckillGoodsKey);
        //5.删除本地缓存中商品状态位
        CacheHelper.removeAll();
    }
}
